1 /*
2 * Copyright (C) 2009 The Guava Authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package com.google.common.collect;
18
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static com.google.common.base.Preconditions.checkElementIndex;
21 import static com.google.common.base.Preconditions.checkNotNull;
22
23 import com.google.common.annotations.Beta;
24 import com.google.common.annotations.GwtCompatible;
25 import com.google.common.base.Objects;
26
27 import java.io.Serializable;
28 import java.util.Arrays;
29 import java.util.Collection;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Set;
34
35 import javax.annotation.Nullable;
36
37 /**
38 * Fixed-size {@link Table} implementation backed by a two-dimensional array.
39 *
40 * <p>The allowed row and column keys must be supplied when the table is
41 * created. The table always contains a mapping for every row key / column pair.
42 * The value corresponding to a given row and column is null unless another
43 * value is provided.
44 *
45 * <p>The table's size is constant: the product of the number of supplied row
46 * keys and the number of supplied column keys. The {@code remove} and {@code
47 * clear} methods are not supported by the table or its views. The {@link
48 * #erase} and {@link #eraseAll} methods may be used instead.
49 *
50 * <p>The ordering of the row and column keys provided when the table is
51 * constructed determines the iteration ordering across rows and columns in the
52 * table's views. None of the view iterators support {@link Iterator#remove}.
53 * If the table is modified after an iterator is created, the iterator remains
54 * valid.
55 *
56 * <p>This class requires less memory than the {@link HashBasedTable} and {@link
57 * TreeBasedTable} implementations, except when the table is sparse.
58 *
59 * <p>Null row keys or column keys are not permitted.
60 *
61 * <p>This class provides methods involving the underlying array structure,
62 * where the array indices correspond to the position of a row or column in the
63 * lists of allowed keys and values. See the {@link #at}, {@link #set}, {@link
64 * #toArray}, {@link #rowKeyList}, and {@link #columnKeyList} methods for more
65 * details.
66 *
67 * <p>Note that this implementation is not synchronized. If multiple threads
68 * access the same cell of an {@code ArrayTable} concurrently and one of the
69 * threads modifies its value, there is no guarantee that the new value will be
70 * fully visible to the other threads. To guarantee that modifications are
71 * visible, synchronize access to the table. Unlike other {@code Table}
72 * implementations, synchronization is unnecessary between a thread that writes
73 * to one cell and a thread that reads from another.
74 *
75 * <p>See the Guava User Guide article on <a href=
76 * "http://code.google.com/p/guava-libraries/wiki/NewCollectionTypesExplained#Table">
77 * {@code Table}</a>.
78 *
79 * @author Jared Levy
80 * @since 10.0
81 */
82 @Beta
83 @GwtCompatible(emulated = true)
84 public final class ArrayTable<R, C, V> extends AbstractTable<R, C, V> implements Serializable {
85
86 /**
87 * Creates an empty {@code ArrayTable}.
88 *
89 * @param rowKeys row keys that may be stored in the generated table
90 * @param columnKeys column keys that may be stored in the generated table
91 * @throws NullPointerException if any of the provided keys is null
92 * @throws IllegalArgumentException if {@code rowKeys} or {@code columnKeys}
93 * contains duplicates or is empty
94 */
95 public static <R, C, V> ArrayTable<R, C, V> create(
96 Iterable<? extends R> rowKeys, Iterable<? extends C> columnKeys) {
97 return new ArrayTable<R, C, V>(rowKeys, columnKeys);
98 }
99
100 /*
101 * TODO(jlevy): Add factory methods taking an Enum class, instead of an
102 * iterable, to specify the allowed row keys and/or column keys. Note that
103 * custom serialization logic is needed to support different enum sizes during
104 * serialization and deserialization.
105 */
106
107 /**
108 * Creates an {@code ArrayTable} with the mappings in the provided table.
109 *
110 * <p>If {@code table} includes a mapping with row key {@code r} and a
111 * separate mapping with column key {@code c}, the returned table contains a
112 * mapping with row key {@code r} and column key {@code c}. If that row key /
113 * column key pair in not in {@code table}, the pair maps to {@code null} in
114 * the generated table.
115 *
116 * <p>The returned table allows subsequent {@code put} calls with the row keys
117 * in {@code table.rowKeySet()} and the column keys in {@code
118 * table.columnKeySet()}. Calling {@link #put} with other keys leads to an
119 * {@code IllegalArgumentException}.
120 *
121 * <p>The ordering of {@code table.rowKeySet()} and {@code
122 * table.columnKeySet()} determines the row and column iteration ordering of
123 * the returned table.
124 *
125 * @throws NullPointerException if {@code table} has a null key
126 * @throws IllegalArgumentException if the provided table is empty
127 */
128 public static <R, C, V> ArrayTable<R, C, V> create(Table<R, C, V> table) {
129 return (table instanceof ArrayTable<?, ?, ?>)
130 ? new ArrayTable<R, C, V>((ArrayTable<R, C, V>) table)
131 : new ArrayTable<R, C, V>(table);
132 }
133
134 private final ImmutableList<R> rowList;
135 private final ImmutableList<C> columnList;
136
137 // TODO(jlevy): Add getters returning rowKeyToIndex and columnKeyToIndex?
138 private final ImmutableMap<R, Integer> rowKeyToIndex;
139 private final ImmutableMap<C, Integer> columnKeyToIndex;
140 private final V[][] array;
141
142 private ArrayTable(Iterable<? extends R> rowKeys,
143 Iterable<? extends C> columnKeys) {
144 this.rowList = ImmutableList.copyOf(rowKeys);
145 this.columnList = ImmutableList.copyOf(columnKeys);
146 checkArgument(!rowList.isEmpty());
147 checkArgument(!columnList.isEmpty());
148
149 /*
150 * TODO(jlevy): Support empty rowKeys or columnKeys? If we do, when
151 * columnKeys is empty but rowKeys isn't, the table is empty but
152 * containsRow() can return true and rowKeySet() isn't empty.
153 */
154 rowKeyToIndex = index(rowList);
155 columnKeyToIndex = index(columnList);
156
157 @SuppressWarnings("unchecked")
158 V[][] tmpArray
159 = (V[][]) new Object[rowList.size()][columnList.size()];
160 array = tmpArray;
161 // Necessary because in GWT the arrays are initialized with "undefined" instead of null.
162 eraseAll();
163 }
164
165 private static <E> ImmutableMap<E, Integer> index(List<E> list) {
166 ImmutableMap.Builder<E, Integer> columnBuilder = ImmutableMap.builder();
167 for (int i = 0; i < list.size(); i++) {
168 columnBuilder.put(list.get(i), i);
169 }
170 return columnBuilder.build();
171 }
172
173 private ArrayTable(Table<R, C, V> table) {
174 this(table.rowKeySet(), table.columnKeySet());
175 putAll(table);
176 }
177
178 private ArrayTable(ArrayTable<R, C, V> table) {
179 rowList = table.rowList;
180 columnList = table.columnList;
181 rowKeyToIndex = table.rowKeyToIndex;
182 columnKeyToIndex = table.columnKeyToIndex;
183 @SuppressWarnings("unchecked")
184 V[][] copy = (V[][]) new Object[rowList.size()][columnList.size()];
185 array = copy;
186 // Necessary because in GWT the arrays are initialized with "undefined" instead of null.
187 eraseAll();
188 for (int i = 0; i < rowList.size(); i++) {
189 System.arraycopy(table.array[i], 0, copy[i], 0, table.array[i].length);
190 }
191 }
192
193 private abstract static class ArrayMap<K, V> extends Maps.ImprovedAbstractMap<K, V> {
194 private final ImmutableMap<K, Integer> keyIndex;
195
196 private ArrayMap(ImmutableMap<K, Integer> keyIndex) {
197 this.keyIndex = keyIndex;
198 }
199
200 @Override
201 public Set<K> keySet() {
202 return keyIndex.keySet();
203 }
204
205 K getKey(int index) {
206 return keyIndex.keySet().asList().get(index);
207 }
208
209 abstract String getKeyRole();
210
211 @Nullable abstract V getValue(int index);
212
213 @Nullable abstract V setValue(int index, V newValue);
214
215 @Override
216 public int size() {
217 return keyIndex.size();
218 }
219
220 @Override
221 public boolean isEmpty() {
222 return keyIndex.isEmpty();
223 }
224
225 @Override
226 protected Set<Entry<K, V>> createEntrySet() {
227 return new Maps.EntrySet<K, V>() {
228 @Override
229 Map<K, V> map() {
230 return ArrayMap.this;
231 }
232
233 @Override
234 public Iterator<Entry<K, V>> iterator() {
235 return new AbstractIndexedListIterator<Entry<K, V>>(size()) {
236 @Override
237 protected Entry<K, V> get(final int index) {
238 return new AbstractMapEntry<K, V>() {
239 @Override
240 public K getKey() {
241 return ArrayMap.this.getKey(index);
242 }
243
244 @Override
245 public V getValue() {
246 return ArrayMap.this.getValue(index);
247 }
248
249 @Override
250 public V setValue(V value) {
251 return ArrayMap.this.setValue(index, value);
252 }
253 };
254 }
255 };
256 }
257 };
258 }
259
260 // TODO(user): consider an optimized values() implementation
261
262 @Override
263 public boolean containsKey(@Nullable Object key) {
264 return keyIndex.containsKey(key);
265 }
266
267 @Override
268 public V get(@Nullable Object key) {
269 Integer index = keyIndex.get(key);
270 if (index == null) {
271 return null;
272 } else {
273 return getValue(index);
274 }
275 }
276
277 @Override
278 public V put(K key, V value) {
279 Integer index = keyIndex.get(key);
280 if (index == null) {
281 throw new IllegalArgumentException(
282 getKeyRole() + " " + key + " not in " + keyIndex.keySet());
283 }
284 return setValue(index, value);
285 }
286
287 @Override
288 public V remove(Object key) {
289 throw new UnsupportedOperationException();
290 }
291
292 @Override
293 public void clear() {
294 throw new UnsupportedOperationException();
295 }
296 }
297
298 /**
299 * Returns, as an immutable list, the row keys provided when the table was
300 * constructed, including those that are mapped to null values only.
301 */
302 public ImmutableList<R> rowKeyList() {
303 return rowList;
304 }
305
306 /**
307 * Returns, as an immutable list, the column keys provided when the table was
308 * constructed, including those that are mapped to null values only.
309 */
310 public ImmutableList<C> columnKeyList() {
311 return columnList;
312 }
313
314 /**
315 * Returns the value corresponding to the specified row and column indices.
316 * The same value is returned by {@code
317 * get(rowKeyList().get(rowIndex), columnKeyList().get(columnIndex))}, but
318 * this method runs more quickly.
319 *
320 * @param rowIndex position of the row key in {@link #rowKeyList()}
321 * @param columnIndex position of the row key in {@link #columnKeyList()}
322 * @return the value with the specified row and column
323 * @throws IndexOutOfBoundsException if either index is negative, {@code
324 * rowIndex} is greater then or equal to the number of allowed row keys,
325 * or {@code columnIndex} is greater then or equal to the number of
326 * allowed column keys
327 */
328 public V at(int rowIndex, int columnIndex) {
329 // In GWT array access never throws IndexOutOfBoundsException.
330 checkElementIndex(rowIndex, rowList.size());
331 checkElementIndex(columnIndex, columnList.size());
332 return array[rowIndex][columnIndex];
333 }
334
335 /**
336 * Associates {@code value} with the specified row and column indices. The
337 * logic {@code
338 * put(rowKeyList().get(rowIndex), columnKeyList().get(columnIndex), value)}
339 * has the same behavior, but this method runs more quickly.
340 *
341 * @param rowIndex position of the row key in {@link #rowKeyList()}
342 * @param columnIndex position of the row key in {@link #columnKeyList()}
343 * @param value value to store in the table
344 * @return the previous value with the specified row and column
345 * @throws IndexOutOfBoundsException if either index is negative, {@code
346 * rowIndex} is greater then or equal to the number of allowed row keys,
347 * or {@code columnIndex} is greater then or equal to the number of
348 * allowed column keys
349 */
350 public V set(int rowIndex, int columnIndex, @Nullable V value) {
351 // In GWT array access never throws IndexOutOfBoundsException.
352 checkElementIndex(rowIndex, rowList.size());
353 checkElementIndex(columnIndex, columnList.size());
354 V oldValue = array[rowIndex][columnIndex];
355 array[rowIndex][columnIndex] = value;
356 return oldValue;
357 }
358
359 /**
360 * Not supported. Use {@link #eraseAll} instead.
361 *
362 * @throws UnsupportedOperationException always
363 * @deprecated Use {@link #eraseAll}
364 */
365 @Override
366 @Deprecated public void clear() {
367 throw new UnsupportedOperationException();
368 }
369
370 /**
371 * Associates the value {@code null} with every pair of allowed row and column
372 * keys.
373 */
374 public void eraseAll() {
375 for (V[] row : array) {
376 Arrays.fill(row, null);
377 }
378 }
379
380 /**
381 * Returns {@code true} if the provided keys are among the keys provided when
382 * the table was constructed.
383 */
384 @Override
385 public boolean contains(@Nullable Object rowKey, @Nullable Object columnKey) {
386 return containsRow(rowKey) && containsColumn(columnKey);
387 }
388
389 /**
390 * Returns {@code true} if the provided column key is among the column keys
391 * provided when the table was constructed.
392 */
393 @Override
394 public boolean containsColumn(@Nullable Object columnKey) {
395 return columnKeyToIndex.containsKey(columnKey);
396 }
397
398 /**
399 * Returns {@code true} if the provided row key is among the row keys
400 * provided when the table was constructed.
401 */
402 @Override
403 public boolean containsRow(@Nullable Object rowKey) {
404 return rowKeyToIndex.containsKey(rowKey);
405 }
406
407 @Override
408 public boolean containsValue(@Nullable Object value) {
409 for (V[] row : array) {
410 for (V element : row) {
411 if (Objects.equal(value, element)) {
412 return true;
413 }
414 }
415 }
416 return false;
417 }
418
419 @Override
420 public V get(@Nullable Object rowKey, @Nullable Object columnKey) {
421 Integer rowIndex = rowKeyToIndex.get(rowKey);
422 Integer columnIndex = columnKeyToIndex.get(columnKey);
423 return (rowIndex == null || columnIndex == null)
424 ? null : at(rowIndex, columnIndex);
425 }
426
427 /**
428 * Always returns {@code false}.
429 */
430 @Override
431 public boolean isEmpty() {
432 return false;
433 }
434
435 /**
436 * {@inheritDoc}
437 *
438 * @throws IllegalArgumentException if {@code rowKey} is not in {@link
439 * #rowKeySet()} or {@code columnKey} is not in {@link #columnKeySet()}.
440 */
441 @Override
442 public V put(R rowKey, C columnKey, @Nullable V value) {
443 checkNotNull(rowKey);
444 checkNotNull(columnKey);
445 Integer rowIndex = rowKeyToIndex.get(rowKey);
446 checkArgument(rowIndex != null, "Row %s not in %s", rowKey, rowList);
447 Integer columnIndex = columnKeyToIndex.get(columnKey);
448 checkArgument(columnIndex != null,
449 "Column %s not in %s", columnKey, columnList);
450 return set(rowIndex, columnIndex, value);
451 }
452
453 /*
454 * TODO(jlevy): Consider creating a merge() method, similar to putAll() but
455 * copying non-null values only.
456 */
457
458 /**
459 * {@inheritDoc}
460 *
461 * <p>If {@code table} is an {@code ArrayTable}, its null values will be
462 * stored in this table, possibly replacing values that were previously
463 * non-null.
464 *
465 * @throws NullPointerException if {@code table} has a null key
466 * @throws IllegalArgumentException if any of the provided table's row keys or
467 * column keys is not in {@link #rowKeySet()} or {@link #columnKeySet()}
468 */
469 @Override
470 public void putAll(Table<? extends R, ? extends C, ? extends V> table) {
471 super.putAll(table);
472 }
473
474 /**
475 * Not supported. Use {@link #erase} instead.
476 *
477 * @throws UnsupportedOperationException always
478 * @deprecated Use {@link #erase}
479 */
480 @Override
481 @Deprecated public V remove(Object rowKey, Object columnKey) {
482 throw new UnsupportedOperationException();
483 }
484
485 /**
486 * Associates the value {@code null} with the specified keys, assuming both
487 * keys are valid. If either key is null or isn't among the keys provided
488 * during construction, this method has no effect.
489 *
490 * <p>This method is equivalent to {@code put(rowKey, columnKey, null)} when
491 * both provided keys are valid.
492 *
493 * @param rowKey row key of mapping to be erased
494 * @param columnKey column key of mapping to be erased
495 * @return the value previously associated with the keys, or {@code null} if
496 * no mapping existed for the keys
497 */
498 public V erase(@Nullable Object rowKey, @Nullable Object columnKey) {
499 Integer rowIndex = rowKeyToIndex.get(rowKey);
500 Integer columnIndex = columnKeyToIndex.get(columnKey);
501 if (rowIndex == null || columnIndex == null) {
502 return null;
503 }
504 return set(rowIndex, columnIndex, null);
505 }
506
507 // TODO(jlevy): Add eraseRow and eraseColumn methods?
508
509 @Override
510 public int size() {
511 return rowList.size() * columnList.size();
512 }
513
514 /**
515 * Returns an unmodifiable set of all row key / column key / value
516 * triplets. Changes to the table will update the returned set.
517 *
518 * <p>The returned set's iterator traverses the mappings with the first row
519 * key, the mappings with the second row key, and so on.
520 *
521 * <p>The value in the returned cells may change if the table subsequently
522 * changes.
523 *
524 * @return set of table cells consisting of row key / column key / value
525 * triplets
526 */
527 @Override
528 public Set<Cell<R, C, V>> cellSet() {
529 return super.cellSet();
530 }
531
532 @Override
533 Iterator<Cell<R, C, V>> cellIterator() {
534 return new AbstractIndexedListIterator<Cell<R, C, V>>(size()) {
535 @Override protected Cell<R, C, V> get(final int index) {
536 return new Tables.AbstractCell<R, C, V>() {
537 final int rowIndex = index / columnList.size();
538 final int columnIndex = index % columnList.size();
539 @Override
540 public R getRowKey() {
541 return rowList.get(rowIndex);
542 }
543 @Override
544 public C getColumnKey() {
545 return columnList.get(columnIndex);
546 }
547 @Override
548 public V getValue() {
549 return at(rowIndex, columnIndex);
550 }
551 };
552 }
553 };
554 }
555
556 /**
557 * Returns a view of all mappings that have the given column key. If the
558 * column key isn't in {@link #columnKeySet()}, an empty immutable map is
559 * returned.
560 *
561 * <p>Otherwise, for each row key in {@link #rowKeySet()}, the returned map
562 * associates the row key with the corresponding value in the table. Changes
563 * to the returned map will update the underlying table, and vice versa.
564 *
565 * @param columnKey key of column to search for in the table
566 * @return the corresponding map from row keys to values
567 */
568 @Override
569 public Map<R, V> column(C columnKey) {
570 checkNotNull(columnKey);
571 Integer columnIndex = columnKeyToIndex.get(columnKey);
572 return (columnIndex == null)
573 ? ImmutableMap.<R, V>of() : new Column(columnIndex);
574 }
575
576 private class Column extends ArrayMap<R, V> {
577 final int columnIndex;
578
579 Column(int columnIndex) {
580 super(rowKeyToIndex);
581 this.columnIndex = columnIndex;
582 }
583
584 @Override
585 String getKeyRole() {
586 return "Row";
587 }
588
589 @Override
590 V getValue(int index) {
591 return at(index, columnIndex);
592 }
593
594 @Override
595 V setValue(int index, V newValue) {
596 return set(index, columnIndex, newValue);
597 }
598 }
599
600 /**
601 * Returns an immutable set of the valid column keys, including those that
602 * are associated with null values only.
603 *
604 * @return immutable set of column keys
605 */
606 @Override
607 public ImmutableSet<C> columnKeySet() {
608 return columnKeyToIndex.keySet();
609 }
610
611 private transient ColumnMap columnMap;
612
613 @Override
614 public Map<C, Map<R, V>> columnMap() {
615 ColumnMap map = columnMap;
616 return (map == null) ? columnMap = new ColumnMap() : map;
617 }
618
619 private class ColumnMap extends ArrayMap<C, Map<R, V>> {
620 private ColumnMap() {
621 super(columnKeyToIndex);
622 }
623
624 @Override
625 String getKeyRole() {
626 return "Column";
627 }
628
629 @Override
630 Map<R, V> getValue(int index) {
631 return new Column(index);
632 }
633
634 @Override
635 Map<R, V> setValue(int index, Map<R, V> newValue) {
636 throw new UnsupportedOperationException();
637 }
638
639 @Override
640 public Map<R, V> put(C key, Map<R, V> value) {
641 throw new UnsupportedOperationException();
642 }
643 }
644
645 /**
646 * Returns a view of all mappings that have the given row key. If the
647 * row key isn't in {@link #rowKeySet()}, an empty immutable map is
648 * returned.
649 *
650 * <p>Otherwise, for each column key in {@link #columnKeySet()}, the returned
651 * map associates the column key with the corresponding value in the
652 * table. Changes to the returned map will update the underlying table, and
653 * vice versa.
654 *
655 * @param rowKey key of row to search for in the table
656 * @return the corresponding map from column keys to values
657 */
658 @Override
659 public Map<C, V> row(R rowKey) {
660 checkNotNull(rowKey);
661 Integer rowIndex = rowKeyToIndex.get(rowKey);
662 return (rowIndex == null) ? ImmutableMap.<C, V>of() : new Row(rowIndex);
663 }
664
665 private class Row extends ArrayMap<C, V> {
666 final int rowIndex;
667
668 Row(int rowIndex) {
669 super(columnKeyToIndex);
670 this.rowIndex = rowIndex;
671 }
672
673 @Override
674 String getKeyRole() {
675 return "Column";
676 }
677
678 @Override
679 V getValue(int index) {
680 return at(rowIndex, index);
681 }
682
683 @Override
684 V setValue(int index, V newValue) {
685 return set(rowIndex, index, newValue);
686 }
687 }
688
689 /**
690 * Returns an immutable set of the valid row keys, including those that are
691 * associated with null values only.
692 *
693 * @return immutable set of row keys
694 */
695 @Override
696 public ImmutableSet<R> rowKeySet() {
697 return rowKeyToIndex.keySet();
698 }
699
700 private transient RowMap rowMap;
701
702 @Override
703 public Map<R, Map<C, V>> rowMap() {
704 RowMap map = rowMap;
705 return (map == null) ? rowMap = new RowMap() : map;
706 }
707
708 private class RowMap extends ArrayMap<R, Map<C, V>> {
709 private RowMap() {
710 super(rowKeyToIndex);
711 }
712
713 @Override
714 String getKeyRole() {
715 return "Row";
716 }
717
718 @Override
719 Map<C, V> getValue(int index) {
720 return new Row(index);
721 }
722
723 @Override
724 Map<C, V> setValue(int index, Map<C, V> newValue) {
725 throw new UnsupportedOperationException();
726 }
727
728 @Override
729 public Map<C, V> put(R key, Map<C, V> value) {
730 throw new UnsupportedOperationException();
731 }
732 }
733
734 /**
735 * Returns an unmodifiable collection of all values, which may contain
736 * duplicates. Changes to the table will update the returned collection.
737 *
738 * <p>The returned collection's iterator traverses the values of the first row
739 * key, the values of the second row key, and so on.
740 *
741 * @return collection of values
742 */
743 @Override
744 public Collection<V> values() {
745 return super.values();
746 }
747
748 private static final long serialVersionUID = 0;
749 }